

import Metal
import MetalKit

struct Vertex {
    var position: SIMD2<Float>
    var textureCoordinate: SIMD2<Float>
}

class Render: NSObject {
    private var metalView: MTKView
    private var device: MTLDevice {
        return metalView.device!
    }
    private var vertexBuffer: MTLBuffer!
    private var renderPipelineState: MTLRenderPipelineState!
    private var commandQueue: MTLCommandQueue!
    private var texture: MTLTexture!
    private var viewportSize: SIMD2<Float> = [0.0, 0.0]
    
    @objc init(_ metalView: MTKView) {
        self.metalView = metalView
        super.init()
        metalView.delegate = self
        // Set up a descriptor for creating a pipeline state object
        
        guard let library = try? device.makeDefaultLibrary(bundle: Bundle.module)
        else { fatalError("Unable to create default library") }
        let vertex_func = library.makeFunction(name: "vertextShader")
        let frag_func = library.makeFunction(name: "samplingShader")
        let rpld = MTLRenderPipelineDescriptor()
        rpld.vertexFunction = vertex_func
        rpld.fragmentFunction = frag_func
        rpld.colorAttachments[0].pixelFormat = metalView.colorPixelFormat
        
        do {
            renderPipelineState = try device.makeRenderPipelineState(descriptor: rpld)
        } catch {
            print(error)
        }
        commandQueue = device.makeCommandQueue()
        assert(commandQueue != nil, "Failed to create command queue")
    }
}

extension Render: MTKViewDelegate {
    func mtkView(_ view: MTKView, drawableSizeWillChange size: CGSize) {
        print("drawableSizeWillChange \(size)")        
        viewportSize.x = Float(size.width)
        viewportSize.y = Float(size.height)
    }
    func draw(in view: MTKView) {
        updateVertex()
        
        if let drawable = view.currentDrawable {
            let rpd = MTLRenderPassDescriptor()
            let bleen = MTLClearColor(red: 0, green: 0.5, blue: 0.5, alpha: 1)
            rpd.colorAttachments[0].texture = drawable.texture
            rpd.colorAttachments[0].clearColor = bleen
            rpd.colorAttachments[0].loadAction = .clear
            
            let commandBuffer = commandQueue.makeCommandBuffer()
            let command_encoder = commandBuffer?.makeRenderCommandEncoder(descriptor: rpd)
            command_encoder?.setViewport(MTLViewport(originX: 0, originY: 0, width: Double(viewportSize.x), height: Double(viewportSize.y), znear: -1, zfar: 1))
            command_encoder?.setRenderPipelineState(renderPipelineState)
            command_encoder?.setVertexBuffer(vertexBuffer, offset: 0, index: 0)
            command_encoder?.setVertexBytes(&viewportSize, length: MemoryLayout<SIMD2<Float>>.size, index: 1)
            command_encoder?.setFragmentTexture(texture, index: 0)
            command_encoder?.drawPrimitives(type: .triangle, vertexStart: 0, vertexCount: 6)
            command_encoder?.endEncoding()
            commandBuffer?.present(drawable)
            commandBuffer?.commit()
        }
    }
}


extension Render {
    private func updateVertex() {
        // fit width
        let fHalfImageWidth: Float = Float(viewportSize.x) / 2
        let fHlfImageHeight: Float = Float(viewportSize.y) / 2
        let vertex_data = [
            Vertex(position: [ fHalfImageWidth, -fHlfImageHeight], textureCoordinate: [1.0, 1.0]),
            Vertex(position: [-fHalfImageWidth, -fHlfImageHeight], textureCoordinate: [0.0, 1.0]),
            Vertex(position: [-fHalfImageWidth,  fHlfImageHeight], textureCoordinate: [0.0, 0.0]),
            Vertex(position: [ fHalfImageWidth, -fHlfImageHeight], textureCoordinate: [1.0, 1.0]),
            Vertex(position: [-fHalfImageWidth,  fHlfImageHeight], textureCoordinate: [0.0, 0.0]),
            Vertex(position: [ fHalfImageWidth,  fHlfImageHeight], textureCoordinate: [1.0, 0.0]),
        ]
        let data_size = vertex_data.count * MemoryLayout<Vertex>.size
        vertexBuffer = device.makeBuffer(bytes: vertex_data, length: data_size, options: [])
    }
    
    private func loadTexture(name: String) -> MTLTexture!{
        let path = Bundle.main.path(forResource: name, ofType: "jpg")
        let url = URL(fileURLWithPath: path!)
        let textureLoader = MTKTextureLoader(device: device)
        var texture: MTLTexture!
        do {
            texture = try textureLoader.newTexture(URL: url, options: [:])
        } catch {
            print("can not load texture \(error)")
        }
        return texture
    }
}

// MARK: - Public Method
extension Render {
    public func updateTexture(name: String) {
        // load new texture
        self.texture = loadTexture(name: name)
        metalView.setNeedsDisplay()
    }
}
